using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Remoting.Messaging;

//TODO: ADDS SR class to project.

namespace FileCollection
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T">Extra Information about the data</typeparam>
    public class StreamFileList<T> : FileCollectionBase<LinkedList<Guid>, T>, IQueue
    {
        #region Constants

        //private const int DefaultBufferSize = 0x1000; //4kb

        #endregion

        #region Variables

        #endregion

        #region Properties

        public T First
        {
            get
            {
                if (Collection.First != null)
                {
                    Guid id = Collection.First.Value;
                    ExtraInformation<T> info = LoadInfoFile(id, false);
                    return info.ExtraInfo;
                }
                return default(T);
            }
        }

        public T Last
        {
            get
            {
                if (Collection.Last != null)
                {
                    Guid id = Collection.Last.Value;
                    ExtraInformation<T> info = LoadInfoFile(id, false);
                    return info.ExtraInfo;
                }
                return default(T);
            }
        }

        #endregion

        #region Constructor

        public StreamFileList(string directoryName, string listName)
            : this(directoryName, listName, false)
        {
        }

        public StreamFileList(string directoryPath, string name, bool automaticSave)
            : base(directoryPath, name, automaticSave)
        {
            CollectionType = "StreamFileList";
            Version = 1;
        }

        #endregion

        #region Public Methods

        public void Flush()
        {
            ValidateException();
            SaveCollection();
        }

        public void Open()
        {
            Closed = false;
            ValidateException();
            LoadCollection();
        }

        public void Close()
        {
            lock (SyncRoot)
            {
                ValidateException();
                Closed = true;
                SaveCollection();
                Collection.Clear();
            }
        }

        public void Clear()
        {
            ValidateException();
            //PARA MELHORAR O DESEMPENHO, COPIO A LISTA PARA UM ARRAY E DEPOIS LIMPO ASSIM MINIMIZANDO O TEMPO QUE O OBJETO FICA BLOQUEADO.
            Guid[] guids;

            lock (SyncRoot)
            {
                guids = new Guid[Collection.Count];
                Collection.CopyTo(guids, 0);
                Collection.Clear();
            }

            SaveCollection();

            foreach (Guid id in guids)
            {
                File.Delete(GetItemFileName(id));
                File.Delete(GetItemInfoFileName(id));
            }
        }

        public T[] GetInformationList()
        {
            //System.Threading.ReaderWriterLock a = new ReaderWriterLock();
            lock (SyncRoot)
            {
                var lista = new T[Collection.Count];
                LinkedListNode<Guid> item = Collection.First;
                int i = 0;
                do
                {
                    ExtraInformation<T> info = LoadInfoFile(item.Value, false);
                    if (info != null && !Equals(info.ExtraInfo, default(T)))
                        lista[i] = info.ExtraInfo;

                    item = item.Next;
                    i++;
                } while (item != null);

                return lista;
            }
        }

        public Stream GetItem(Guid id, out T info)
        {
            LinkedListNode<Guid> item;
            lock (SyncRoot)
            {
                item = Collection.Find(id);
            }

            if (item != null)
            {
                Stream stream = LoadDataInfo(id, out info, false);
                if (stream != null && !Equals(info, default(T)))
                    return stream;
            }

            info = default(T);
            return null;
        }

        public Stream GetFirst(out T info)
        {
            ValidateException();
            while (Collection.Count > 0)
            {
                Guid id;
                lock (SyncRoot)
                {
                    id = Collection.First.Value;
                }

                Stream stream = LoadDataInfo(id, out info, false);
                if (stream != null && !Equals(info, default(T)))
                    return stream;
            }

            info = default(T);
            return null;
        }

        public Stream GetLast(out T info)
        {
            ValidateException();
            while (Collection.Count > 0)
            {
                Guid id;
                lock (SyncRoot)
                {
                    id = Collection.Last.Value;
                }

                Stream stream = LoadDataInfo(id, out info, false);
                if (stream != null && !Equals(info, default(T)))
                    return stream;
            }

            info = default(T);
            return null;
        }

        public ExtraInformation<T>[] GetExtraInformationList()
        {
            //System.Threading.ReaderWriterLock a = new ReaderWriterLock();
            lock (SyncRoot)
            {
                var lista = new ExtraInformation<T>[Collection.Count];
                LinkedListNode<Guid> item = Collection.First;
                int i = 0;
                do
                {
                    ExtraInformation<T> info = LoadInfoFile(item.Value, false);
                    if (info != null)
                        lista[i] = info;

                    item = item.Next;
                    i++;
                } while (item != null);

                return lista;
            }
        }

        public bool DequeueToAnotherQueue(StreamFileQueue<T> toQueue)
        {
            ValidateException();

            if (toQueue == null)
                throw new ArgumentNullException("targetQueue");

            T info;
            Stream stream = Dequeue(out info);
            if (stream != null)
            {
                toQueue.Enqueue(stream, info);
                return true;
            }
            return false;
        }

        public Guid DequeueToAnotherList(StreamFileList<T> toList)
        {
            ValidateException();
            if (toList == null)
                throw new ArgumentNullException("targetList");

            T info;
            Stream stream = Dequeue(out info);
            return stream != null ? toList.AddLast(stream, info) : Guid.Empty;
        }


        public void RemoveFirst()
        {
            ValidateException();
            while (Collection.Count > 0)
            {
                lock (SyncRoot)
                {
                    Guid id = Collection.First.Value;
                    Collection.RemoveFirst();
                    RemoveDataFile(id);
                    RemoveInfoFile(id);
                }
            }
        }

        public void RemoveLast()
        {
            ValidateException();
            while (Collection.Count > 0)
            {
                lock (SyncRoot)
                {
                    Guid id = Collection.Last.Value;
                    Collection.RemoveLast();
                    RemoveDataFile(id);
                    RemoveInfoFile(id);
                }
            }
        }

        public void Remove(Guid id)
        {
            LinkedListNode<Guid> item = Collection.Find(id);
            if (item != null)
            {
                lock (SyncRoot)
                {
                    id = item.Value;
                    Collection.Remove(id);
                    RemoveDataFile(id);
                    RemoveInfoFile(id);
                }
            }
        }

        public void Append(Guid id, Stream stream)
        {
            ValidateException();
            LinkedListNode<Guid> item = Collection.Find(id);

            if (item != null)
            {
                lock (item)
                {
                    AppendAndSaveDataFile(item.Value, stream);
                }
            }
        }

        public Stream Dequeue(out T info)
        {
            ValidateException();
            while (Collection.Count > 0)
            {
                Guid id;
                lock (SyncRoot)
                {
                    id = Collection.First.Value;
                    Collection.RemoveFirst();
                }

                Stream stream = LoadDataInfo(id, out info, true);
                if (stream != null && !Equals(info, default(T))) //testar antes era != null
                    return stream;
            }

            info = default(T);
            return null;
        }

        public bool Exists(T info)
        {
            T[] infos = GetInformationList();
            foreach (T localInfo in infos)
            {
                if (localInfo.Equals(info))
                    return true;
            }
            return false;
        }

        public bool Exists(Guid id)
        {
            lock (SyncRoot)
            {
                LinkedListNode<Guid> item = Collection.Find(id);
                return item != null;
            }
        }

        public Guid AddBefore(Guid id, Stream stream, T info)
        {
            ValidateException();

            if (id == Guid.Empty)
                throw new ArgumentNullException("id");

            if (stream == null)
                throw new ArgumentNullException("stream");


            LinkedListNode<Guid> item = Collection.Find(id);
            if (item != null)
            {
                Guid idNewItem = Guid.NewGuid();

                //COMO AMBAS AS FUNCOES PRECISAM DO STREAM, NAO PODEM SER CONCORRENTES
                SaveInfoFile(idNewItem, GetExtraInfo(idNewItem, info, stream));
                SaveDataFile(idNewItem, stream);

                lock (SyncRoot)
                {
                    Collection.AddBefore(item, idNewItem);
                }

                if (AutomaticSave) SaveCollection();
                return idNewItem;
            }

            return Guid.Empty;
        }

        public Guid AddAfter(Guid id, Stream stream, T info)
        {
            ValidateException();
            if (stream == null)
                throw new ArgumentNullException("stream");

            LinkedListNode<Guid> item = Collection.Find(id);
            if (item != null)
            {
                Guid idNewItem = Guid.NewGuid();

                //COMO AMBAS AS FUNCOES PRECISAM DO STREAM, NAO PODEM SER CONCORRENTES
                SaveInfoFile(idNewItem, GetExtraInfo(idNewItem, info, stream));
                SaveDataFile(idNewItem, stream);

                lock (SyncRoot)
                {
                    Collection.AddAfter(item, idNewItem);
                }

                if (AutomaticSave) SaveCollection();
                return idNewItem;
            }

            return Guid.Empty;
        }

        public T Find(Guid id)
        {
            LinkedListNode<Guid> item = Collection.Find(id);

            if (item != null)
            {
                ExtraInformation<T> info = LoadInfoFile(item.Value, false);
                return info.ExtraInfo;
            }

            return default(T);
        }

        public Guid Find(T info)
        {
            ExtraInformation<T>[] infos = GetExtraInformationList();
            foreach (var localInfo in infos)
            {
                if (localInfo.ExtraInfo.Equals(info))
                    return localInfo.Id;
            }

            return Guid.Empty;
        }

        public Guid AddLast(Stream stream, T info)
        {
            ValidateException();
            if (stream == null)
                throw new ArgumentNullException("stream");

            Guid id = Guid.NewGuid();

            //COMO AMBAS AS FUNCOES PRECISAM DO STREAM, NAO PODEM SER CONCORRENTES
            SaveInfoFile(id, GetExtraInfo(id, info, stream));
            SaveDataFile(id, stream);

            lock (SyncRoot)
            {
                Collection.AddLast(id);
            }

            if (AutomaticSave) SaveCollection();
            return id;
        }

        public Guid AddFirst(Stream stream, T info)
        {
            ValidateException();
            if (stream == null)
                throw new ArgumentNullException("stream");

            Guid id = Guid.NewGuid();

            //COMO AMBAS AS FUNCOES PRECISAM DO STREAM, NAO PODEM SER CONCORRENTES
            SaveInfoFile(id, GetExtraInfo(id, info, stream));
            SaveDataFile(id, stream);

            lock (SyncRoot)
            {
                Collection.AddFirst(id);
            }

            if (AutomaticSave) SaveCollection();
            return id;
        }

        /// <summary>
        ///  Enqueue a stream targetName Queue
        /// </summary>
        /// <param name="stream">stream data targetName enqueue</param>
        /// <param name="info">Extra Information about the stream data</param>
        /// <Date>16/05/2007</Date>
        /// <Author>Alan Santos Rodrigues</Author>
        public void Enqueue(Stream stream, T info)
        {
            ValidateException();
            if (stream == null)
                throw new ArgumentNullException("stream");

            AddLast(stream, info);
        }


        //public static FileInfo[] FindCollections(string directoryPath, SearchOption searchOption)
        //{
        //    DirectoryInfo rootPath = new DirectoryInfo(directoryPath);
        //    return rootPath.GetFiles(string.Format("*.{0}", collectionExtension), searchOption);
        //}

        #endregion

        #region Private Methods

        protected override Header[] GetHeaders()
        {
            var headers = new Header[3];
            headers[0] = new Header(HeaderCollectionName, Name, true, "http://www.ons.org.br/Arquitetura/StreamFileList");
            headers[1] = new Header(HeaderVersion, 1, true, "http://www.ons.org.br/Arquitetura/StreamFileList");
            headers[2] = new Header(HeaderCollectionType, "StreamFileList", true,
                                    "http://www.ons.org.br/Arquitetura/StreamFileList");
            return headers;
        }


        protected override void AddToCollection(Guid id)
        {
            Collection.AddFirst(id);
        }

        protected override void CleanCollection()
        {
            Collection.Clear();
        }

        #endregion

        #region Finalizer

        ///<summary>
        ///Allows an  <see cref="StreamFileList{T}" /> to attempt to free resources and perform other cleanup operations before the <see cref="StreamFileList{T}" /> is reclaimed by garbage collection.
        ///</summary>
        ~StreamFileList()
        {
            Dispose(false);
        }

        #endregion
    }
}